package com.superhelper.app.interceptor;

import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.superhelper.app.common.CommonHelper;
import com.superhelper.app.common.Consts;
import com.superhelper.app.domain.Admin;
import com.superhelper.app.domain.Resp;
import com.superhelper.app.service.AdminService;
import com.superhelper.common.service.RedisService;
import com.superhelper.common.utils.CryptUtil;
import com.superhelper.common.utils.JsonHelper;
import com.superhelper.common.utils.RSAUtils;

/**
 * sign校验拦截器
 * 
 */
public class SignInterceptor extends HandlerInterceptorAdapter {

	private static final Logger LOG = LoggerFactory
			.getLogger(SignInterceptor.class);

	@Autowired
	private AdminService userService;

	@Autowired
	private RedisService redis;

	private boolean online;

	private String key;

	public void setKey(String key) {
		this.key = key;
	}

	public void setOnline(boolean online) {
		this.online = online;
	}

	private List<String> uriWhitelist;

	public void setUriWhitelist(List<String> uriWhitelist) {
		this.uriWhitelist = uriWhitelist;
	}

	// 服务降级
	public static Set<String> blockSet = new HashSet<>();

	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		response.setCharacterEncoding("utf-8");
		if (isBlockURI(request.getRequestURI())) {
			return sorry(response);
		}
		String hUserId = request.getHeader("userId");
		Long reqUserId = NumberUtils.toLong(hUserId);
		String plfForm = request.getHeader("device");
		// 如果是uriWhiteList的不需要做token验证
		if (isWhitelist(request.getRequestURI())) {
			// not need do something
			if (plfForm.contains("pcweb")) {
				return true;
			}
		} else {
			if (plfForm.contains("pcweb")) {
				return this.validPcWed(request, response);
			}
			String token = request.getHeader("token");
			// 新版本不能没有token，而且token必须有效
			if (StringUtils.isBlank(token)
					|| false == invalidToken(request, response)) {
				LOG.warn("[token] error token is invalid", token);
				return tokenError(response);
			}
			// 检查参数中的userId，是否与token解出来的一致
			Long tokenUserId = (Long) request.getAttribute("userId");

			// reqUserId > 0 说明请求中有userId，有则比较下userId是否相等
			if (reqUserId > 0) {
				if (tokenUserId.longValue() != reqUserId.longValue()) {
					LOG.warn("[token] error tokenUserId:{} != reqUserId:{}",
							tokenUserId, reqUserId);
					return tokenError(response);
				}
			}
		}
		long time = System.currentTimeMillis();
		Map<String, String[]> params = request.getParameterMap();
		String queryString = "";
		Set<String> set = new TreeSet<String>(params.keySet());
		for (String key : set) { // 遍历所有key
			if ("sign".equalsIgnoreCase(key)) { // 如果是sign就跳出本次循环
				continue;
			}
			String[] values = params.get(key);
			for (int i = 0; i < values.length; i++) { //
				String value = values[i];
				queryString += key + "=" + value + "&";
			}
		}
		String sign = request.getParameter("sign");
		if (StringUtils.isNotBlank(queryString) && StringUtils.isNotBlank(sign)) {
			String toBeEncrypt = queryString + "key=" + key;
			String expectSign = CryptUtil.md5(toBeEncrypt);

			if (StringUtils.equalsIgnoreCase(sign, expectSign)) {
				LOG.info(
						"[sign] uri:{}, toBeEncrypt:{}, expectSign:{},sign:{},cost:{}",
						request.getRequestURI(), toBeEncrypt, expectSign, sign,
						System.currentTimeMillis() - time);
				return true;
			}
			LOG.warn(
					"[sign] uri:{} request failed,toBeEncrypt:{},expectSign:{},sign:{} not match",
					request.getRequestURI(), toBeEncrypt, expectSign, sign);
		}
		return signError(response);
	}

	private boolean isWhitelist(String requestURI) {
		for (String w : uriWhitelist) {
			if (StringUtils.contains(requestURI, w)) {
				return true;
			}
		}
		return false;
	}

	private boolean signError(HttpServletResponse response) throws IOException {
		Resp resp = new Resp();
		resp.setCode(Consts.Code.SIGN_ERROR);
		resp.setMsg("sign error!");
		response.getWriter().write(JsonHelper.toJson(resp));
		return false;
	}

	private boolean tokenError(HttpServletResponse response) throws IOException {
		Resp resp = new Resp();
		resp.setCode(Consts.Code.SECURE_ERROR);
		resp.setMsg("token error!");
		response.getWriter().write(JsonHelper.toJson(resp));
		return false;
	}

	private boolean sessionError(HttpServletResponse response)
			throws IOException {
		Resp resp = new Resp();
		resp.setCode(Consts.Code.SECURE_ERROR);
		resp.setMsg("session invalid!");
		response.getWriter().write(JsonHelper.toJson(resp));
		return false;
	}

	private boolean isBlockURI(String reqURI) {
		for (String str : blockSet) {
			if (StringUtils.contains(reqURI, str)) {
				return true;
			}
		}
		return false;
	}

	private boolean sorry(HttpServletResponse response) throws IOException {
		Resp resp = new Resp();
		resp.setCode(Consts.Code.FAIL);
		response.getWriter().write(JsonHelper.toJson(resp));
		return false;
	}

	private boolean invalidToken(HttpServletRequest request,
			HttpServletResponse response) {

		// 增加token的校验
		String token = StringUtils.trim(request.getHeader("token"));
		LOG.info("[token] token is {}", token);
		String msg = RSAUtils.decrypt(token, online);
		if (msg == null) {
			LOG.warn("[sign] token is invalid msg is null");
			return false;
		}
		String[] arr = msg.split(";");
		if (arr.length < 3) {
			LOG.info(
					"[sign] token is invalid msg length is smaller than 3 ;{}",
					msg);
			return false;
		}
		Long userId = NumberUtils.toLong(arr[0]);
		Long timestamp = NumberUtils.toLong(arr[2]);
		if (userId <= 0 || timestamp <= 0) {
			LOG.info("[sign] token is invalid,userId:{},timestamp:{}", userId,
					timestamp);
			return false;
		}

		// 检查时效，不超过两小时
		if (System.currentTimeMillis() - timestamp > Consts.DAY_AGE * 1000) {
			LOG.info("[sign] token is expired,userId:{},timestamp:{}", userId,
					new Date(timestamp));
			return false;
		}
		request.setAttribute("userId", userId);
		return true;
	}

	private boolean validPcWed(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		String plfForm = request.getHeader("device");
		String hUserId = request.getHeader("userId");
		Long reqUserId = NumberUtils.toLong(hUserId);
		// pc端登录,验证session
		if (plfForm != null && plfForm.contains("pcweb")) {
			Admin user = CommonHelper.getSessionUser(request);
			if (user == null) {
				LOG.warn("[session] is invalid", reqUserId);
				return sessionError(response);
			} else {
				userService.setUserOnline(reqUserId);
				return true;
			}
		}
		return false;
	}

	public static void main(String[] args) {
		System.out.println("2.12.0".compareTo("2.2.0"));
		System.out.println("2.1.0".compareTo("2.1.1"));
		System.out.println(RandomStringUtils.randomAscii(16));
	}
}
